5.05. Сериализация и парсинг
Сериализация и парсинг
JSON: System.Text.Json, Newtonsoft.Json
XML: XmlSerializer, XDocument, LINQ to XML
Бинарная, SOAP-сериализация (кратко)
В HTTP-запросах к API, сохранении настроек, обмене данными между микросервисами, кэшировании (например, в Redis) используется сериализация и десериализация.
Сериализация - это преобразование объекта C# в формат, пригодный для хранения или передачи (например, в строку JSON), а десериализация - восстановление объекта из такого формата.
var user = new { Name = "Timur", Age = 30 };
// Сериализация: объект → JSON
string json = JsonSerializer.Serialize(user);
// Результат: {"Name":"Timur","Age":30}
// Десериализация: JSON → объект
var restored = JsonSerializer.Deserialize<UserDto>(json);
Для работы с JSON испольуется Newtonsoft.Json или System.Text.Json.
Начиная с .NET Core 3.0, Microsoft добавила в ядро мощную библиотеку — System.Text.Json, поэтому «в коробке» есть средство для работы, встроенное в .NET, с высокой производительностью. Но для сложных сценариев лучше использовать Newtonsoft.Json.
Большинство проектов до сих пор используют Newtonsoft.Json (также известный как Json.NET) — самую популярную стороннюю библиотеку для работы с JSON.
using Newtonsoft.Json;
string json = JsonConvert.SerializeObject(user);
User restored = JsonConvert.DeserializeObject<User>(json);
Парсинг JSON - важный навык. Можно записывать какие-то данные в этот формат обмена и передавать, затем читать на сервере, разбивая на сопутствующие элементы и обрабатывая результаты.
Иногда структура JSON неизвестна заранее — например, при интеграции с внешним API. Тогда помогает LINQ-to-JSON. Это динамический парсинг, использующий JObject и JArray (объект и массив соответственно).
string json = @"{
'name': Timur,
'hobbies': ['reading', 'coding'],
'address': { 'city': 'Moscow' }
}";
JObject obj = JObject.Parse(json);
string name = (string)obj["name"];
string city = (string)obj["address"]["city"];
JArray hobbies = (JArray)obj["hobbies"];
Это полезно, когда API возвращает разные поля в зависимости от контекста, или нужно извлечь только часть данных. Также можно писать таким образом универсальный клиент.
Хотя JSON стал стандартом, XML всё ещё используется — в SOAP, конфигурациях, старых API. Хотя частенько можно увидеть комбинацию XML+Java, но всё же и в C# используется из-за распространённости форматов. Для работы с ним используется сериализация XML:
using System.Xml.Serialization;
using System.IO;
var user = new User { Name = "Timur", Age = 25 };
var serializer = new XmlSerializer(typeof(User));
using var writer = new StringWriter();
serializer.Serialize(writer, user);
string xml = writer.ToString();
Как результат:
<User>
<Name>Timur</Name>
<Age>25</Age>
</User>
Иногда нужно получить байтовое представление данных — например, для отправки в HTTP-запросе или сохранения в файл.
string json = JsonSerializer.Serialize(user);
byte[] bytes = Encoding.UTF8.GetBytes(json);
// Отправка в теле запроса
var content = new ByteArrayContent(bytes);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
Приложения редко хранят данные только в памяти. Они обычно используют базы данных для долговременного хранения структурированных данных, файлы для конфигураций, логов, медиа, документов и прочего.
Чтобы работать с этими данными, приложения используют:
- ORM для удобной работы с БД через объекты в коде;
- Файловые операции - чтение/запись файлов напрямую.
Обмен данными - это интеграция, когда одно приложение (или сервис) хочет поделиться данными с другим. Она почти всегда происходит через HTTP-запросы, сообщения (в очередях) или файловые обмены. К примеру, веб-сервис на C# отправляет данные о заказе в систему учёта на Python.
Данные нельзя передать «как есть» - их нужно упаковать в понятный формат:
- Простые объекты (пользователь, заказ) передается в JSON или XML;
- Текст - как строка (plain text);
- Файлы (PDF, изображения) - как поток байтов (бинарные данные).
Это и есть сериализация - преобразование объекта в формат, пригодный для передачи.
Отправка происходит так - объект сериализуется в JSON/байты, затем передаётся по сети.
Получение - данные десериализуются и становятся объектом в принимающем приложении.
Поэтому нужно, чтобы отправитель и получатель были готовы к такой интеграции, подготовив соответствующие системы хранения и обработки, и конечно продумав и реализовав модели, и вся структура должна быть согласована.
Это работает по-разному, к примеру, может быть некий сервис, который просто реализует возможность предоставлять данные из своих баз. Для этого он просто реализует методы со своей внутренней логикой, и публикуется по адресу - эндпоинту. Затем формируется документация, где описывается, какие методы можно вызывать, как обращаться, как должны выглядеть запросы, и как будут выглядеть ответы. В такой ситуации, запрашивающим системам (которые будут интегрироваться) нужно будет просто изучить документацию и обеспечить, чтобы запросы были такими же, как ожидается, а ответы могли обрабатываться и использоваться. И зачастую неважно, как системы это делают внутри - главное чтобы исходящие/входящие данные были корректными.
Бинарные данные - особый случай. PDF, фото, видео - это байты. Их нельзя просто впихнуть в JSON, поэтому существует два подхода:
-
отдельный запрос на загрузку файла - сначала передаётся метаинформация (как раз JSON), а затем файл отправляется отдельно (например, через
multipart/form-data). -
кодирование в строку (Base64), это конечно неэффективно по объёму, но удобно для встраивания, будет выглядеть так:
{
"filename": "report.pdf",
"data": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9MZW5ndGggND..."
}
И всё это в итоге является интеграционным потоком - конвейером, где происходит работа по цепочке:
[Объект в памяти] - [Сериализация] - [Передача] - [Десериализация] - [Обработка]